package com.headStorm.concurrent.lock;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author zhangqq
 * @create 2021-07-22 15:27
 * 参考 https://blog.csdn.net/yucaixiang/article/details/89311527?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EOPENSEARCH%7Edefault-15.base&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EOPENSEARCH%7Edefault-15.base
 **/
public class ReentrantReadWriteLockDemo {

    Map<String, String> map = new ConcurrentHashMap<>();

    public static void main(String[] args) {
//        CacheDemo cacheDemo = new CacheDemo();
//        System.out.println(cacheDemo.getData("dcsc"));
        test();
    }

    public static void test(){
        ReentrantReadWriteLock rtLock = new ReentrantReadWriteLock();
        rtLock.writeLock().lock();
        rtLock.writeLock().unlock();

    }



    /**
     * 结论：上面的测试代码会产生死锁，因为同一个线程中，在没有释放读锁的情况下，就去申请写锁，
     * 这属于锁升级，ReentrantReadWriteLock是不支持的。
     */
    public static void lockShengJi() {
        ReentrantReadWriteLock rtLock = new ReentrantReadWriteLock();
        rtLock.readLock().lock();
        System.out.println("get readLock.");
        rtLock.writeLock().lock();
        System.out.println("blocking");
    }

    /**
     * 结论：ReentrantReadWriteLock支持锁降级， 代码不会产生死锁。这段代码虽然不会导致死锁，
     * 但没有正确的释放锁。从写锁降级成读锁，并不会自动释放当前线程获取的写锁，仍然需要显示的释放，
     * 否则别的线程永远也获取不到写锁。
     */
    public static void lockJiangJi() {
        ReentrantReadWriteLock rtLock = new ReentrantReadWriteLock();
        rtLock.writeLock().lock();
        System.out.println("writeLock");

        rtLock.readLock().lock();
        System.out.println("get read lock");
    }

    public static void lockUse() {
        final Queue3 q3 = new Queue3();
        //同时创建3个读取线程，3个写入线程
        for (int i = 0; i < 3; i++) {
            new Thread() {
                public void run() {
                    while (true) {
                        q3.get();//读取数据
                    }
                }
            }.start();

            new Thread() {
                public void run() {
                    while (true) {
                        q3.put(new Random().nextInt(10000));
                    }
                }

            }.start();
        }
    }

}


class Queue3 {
    private Object data = null;//共享数据，只能有一个线程能写该数据，但可以有多个线程同时读该数据。
    ReadWriteLock rwl = new ReentrantReadWriteLock();//创建读写锁对象

    //读的方法
    public void get() {
        rwl.readLock().lock();//读的时候上读锁
        try {
            System.out.println(Thread.currentThread().getName() + " be ready to read data!");
            Thread.sleep((long) (Math.random() * 1000));
            System.out.println(Thread.currentThread().getName() + "have read data :" + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rwl.readLock().unlock();
        }
    }

    //写的方法
    public void put(Object data) {
        rwl.writeLock().lock();//写的时候上写锁
        try {
            System.out.println(Thread.currentThread().getName() + " be ready to write data!");
            Thread.sleep((long) (Math.random() * 1000));
            this.data = data;
            System.out.println(Thread.currentThread().getName() + " have write data: " + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            rwl.writeLock().unlock();
        }
    }
}

/**
 * 缓存系统就是可以装很多个对象。要拿对象别直接找数据库，而是访问缓存系统。当缓存系统的数据被访问时，就应该检查内部是否有该数据，如果有，
 * 就直接返回，如果没有就查询数据库，然后把数据存入内存中，下次就直接返回该数据不需要再查数据库了。
 */
class CacheDemo {
    //定义一个Map，存储多个对象
    private Map<String, Object> cache = new HashMap<String, Object>();
    public static void main(String[] args) {}
    private ReadWriteLock rwl = new ReentrantReadWriteLock();
    //获取对象的方法，当多个线程来读（即获取对象时）不需要互斥，只有写的时候才需要互斥，因此需要使用读写锁
    public  Object getData(String key){
        rwl.readLock().lock();//先上读锁，这样多个线程都可以来读
        Object value = null;
        try{
            value = cache.get(key);
            //如果线程读的时候发现数据为空，那么就把读锁释放，禁止数据的读取，然后上写锁
            if(value == null){
                rwl.readLock().unlock();
                rwl.writeLock().lock();
                try{
                    //再次判断数据是否存在，因为当一个线程获取写锁完成了数据填充并释放了写锁，另外一个线程也可能获取了写锁，会重复填充数据。
                    if(value == null){
                        value = "aaaa";//实际是去queryDB();即查询数据库
                    }
                }finally{
                    rwl.writeLock().unlock();//数据填充完，释放写锁
                }
                rwl.readLock().lock();//恢复为读锁
            }
        }finally{
            rwl.readLock().unlock();
        }
        return value;
    }
}
